Exercise I: Principal Component Analysis
Recall the mtcars dataset we work with before, which compirses fuel consumption and other aspects of design and performance for 32 cars from 1974. The dataset has 11 dimensions, that is more than it is possible to visualize at the same.
head(mtcars)
- Use
prcomp() to compute a PCA for mtcars. Remember to set the scale parameter, as the variables are in different units and have different ranges
mtcars.pca <- prcomp(mtcars, scale=TRUE)
- Generate a scree plot and note how many dimensions should you retain.
library(factoextra)
Loading required package: ggplot2
Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
# Percent of variance explained:
fviz_eig(mtcars.pca)
Registered S3 method overwritten by 'data.table':
method from
print.data.table

- Compute the percentage of variance explained by each of the principal components.
eig <- mtcars.pca$sdev^2
(var.exp <- 100*eig/sum(eig))
[1] 60.0763659 24.0951627 5.7017934 2.4508858 2.0313737 1.9236011 1.2296544 1.1172858 0.7004241 0.4730495
[11] 0.2004037
- Generate a biplot for the PCA projection. Use the loadings matrix to inspect which variables contributes most to PC1 and which to PC2. What do the PC1 and PC2 correspond to? How are the cars distributed on this representation? Does the "car map" make sense?
fviz_pca_biplot(mtcars.pca) + coord_fixed()

mtcars.pca$rotation
PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9
mpg -0.3625305 0.01612440 -0.22574419 -0.022540255 0.10284468 -0.10879743 0.367723810 -0.754091423 0.235701617
cyl 0.3739160 0.04374371 -0.17531118 -0.002591838 0.05848381 0.16855369 0.057277736 -0.230824925 0.054035270
disp 0.3681852 -0.04932413 -0.06148414 0.256607885 0.39399530 -0.33616451 0.214303077 0.001142134 0.198427848
hp 0.3300569 0.24878402 0.14001476 -0.067676157 0.54004744 0.07143563 -0.001495989 -0.222358441 -0.575830072
drat -0.2941514 0.27469408 0.16118879 0.854828743 0.07732727 0.24449705 0.021119857 0.032193501 -0.046901228
wt 0.3461033 -0.14303825 0.34181851 0.245899314 -0.07502912 -0.46493964 -0.020668302 -0.008571929 0.359498251
qsec -0.2004563 -0.46337482 0.40316904 0.068076532 -0.16466591 -0.33048032 0.050010522 -0.231840021 -0.528377185
vs -0.3065113 -0.23164699 0.42881517 -0.214848616 0.59953955 0.19401702 -0.265780836 0.025935128 0.358582624
am -0.2349429 0.42941765 -0.20576657 -0.030462908 0.08978128 -0.57081745 -0.587305101 -0.059746952 -0.047403982
gear -0.2069162 0.46234863 0.28977993 -0.264690521 0.04832960 -0.24356284 0.605097617 0.336150240 -0.001735039
carb 0.2140177 0.41357106 0.52854459 -0.126789179 -0.36131875 0.18352168 -0.174603192 -0.395629107 0.170640677
PC10 PC11
mpg 0.13928524 -0.124895628
cyl -0.84641949 -0.140695441
disp 0.04937979 0.660606481
hp 0.24782351 -0.256492062
drat -0.10149369 -0.039530246
wt 0.09439426 -0.567448697
qsec -0.27067295 0.181361780
vs -0.15903909 0.008414634
am -0.17778541 0.029823537
gear -0.21382515 -0.053507085
carb 0.07225950 0.319594676
fviz_contrib(mtcars.pca, choice = "var", axes = 1)

fviz_contrib(mtcars.pca, choice = "var", axes = 2)

Exercise 2: Cluster Analysis
Part 1: k-means clustering
We will generate synthetic clustered data to use for k-means clustering.
set.seed(489576)
N <- 1000
C1 <- data.frame(cluster = "C1", x = rnorm(n = N, mean = 1), y = rnorm(n = N, mean = 1))
C2 <- data.frame(cluster = "C2", x = rnorm(n = N, mean = -2), y = rnorm(n = N, mean = -5))
C3 <- data.frame(cluster = "C3", x = rnorm(n = N, mean = 5), y = rnorm(n = N, mean = 1))
DF <- rbind(C1, C2, C3)
ggplot(DF, aes(x, y, color = cluster)) +
geom_point()

- Apply k-means with k = 3 (as you know the true number of clusters). Pring the cluster centers.
kmeans.res <- kmeans(x = DF[, -1], centers = 3)
kmeans.res$centers
x y
1 0.9614141 1.0058486
2 -2.0431190 -4.9932291
3 5.0098346 0.9780214
- Print a confusion map to compare k-means cluster assignment with the true cluster labels.
table(kmeans = kmeans.res$cluster, true = DF$cluster)
true
kmeans C1 C2 C3
1 968 1 23
2 0 999 0
3 32 0 977
- Generate a scatter plot of points, now colored by the cluster assignment.
library(ggplot2)
DF$kmeans <- factor(kmeans.res$cluster)
ggplot(DF, aes(x, y)) +
geom_point(alpha = 0.5, aes( color = kmeans)) +
geom_point(data = data.frame(x = kmeans.res$centers[, 1],
y = kmeans.res$centers[, 2]), size = 3, aes(x, y), color = "Black")

- Now pretend that you don't know the real number of clusters. Use k = 4 and recompute kmeans. Plot the results and see what happened.
kmeans.res2 <- kmeans(x = DF[, -1], centers = 4)
kmeans.res2$centers
x y kmeans
1 5.3301476 1.7351006 3
2 4.7001362 0.2460306 3
3 0.9614141 1.0058486 1
4 -2.0431190 -4.9932291 2
DF$kmeans2 <- factor(kmeans.res2$cluster)
ggplot(DF, aes(x, y, color = kmeans2)) +
geom_point(alpha = 0.5)

Part 2: Hierarchical Clustering
In this exercise you will you use a dataset published in a study by Khan et al. 2001 to perform a hierarchical clustering of the patients in the study based on their overall gene expression data.
This data set consists of expression levels for 2,308 genes. The training and test sets consist of 63 and 20 observations (tissue samples) respectively.
Here, we will use the train set, as we now are only interested in learning how hclust() works. First, load the ISLR where the data is available. The gene expression data is available in an object Khan$xtrain; you can learn more about the data set by typing in ?Khan after loading ISLR package.
library(ISLR)
gene.expression <- Khan$xtrain
dim(gene.expression)
[1] 63 2308
- Compute a (Euclidean) distance matrix between each pair of samples.
D <- dist(gene.expression)
- Perform hierarchical clustering using average linkage.
khan.hclust <- hclust(D, method = "average")
- Plot a dendrogram associated with the hierarchical clustering you just computed. In this example, you actually have the lables of the tissue samples, however, the algorithms was blinded to them. By adding labels to the dendrogram corresponding to
Khan$ytrain, check if the clustering performed groups the observations from same tumor class nearby.
plot(khan.hclust, labels = Khan$ytrain)

Exercise 4: Ads
- Read the data from "https://biox-rbootcamp.github.io/biox-rbootcamp.github.io/assets/lectures/unsupervised_learning/Advertising.csv" containing information on sales of a product and the amount spent on advertising using different media channels.
url <- "https://biox-rbootcamp.github.io/biox-rbootcamp.github.io/assets/lectures/unsupervised_learning/Advertising.csv"
sales <- read.csv(url)
cannot open URL 'https://biox-rbootcamp.github.io/biox-rbootcamp.github.io/assets/lectures/unsupervised_learning/Advertising.csv': HTTP status was '404 Not Found'Error in file(file, "rt") :
cannot open the connection to 'https://biox-rbootcamp.github.io/biox-rbootcamp.github.io/assets/lectures/unsupervised_learning/Advertising.csv'
- Generate a scatterplot of sales against the amount of TV advertising and add a linear fit line.
ggplot(sales, aes(x = TV, y = Sales)) +
geom_point(aes(fill = Radio), color = "grey", pch = 21, size = 3) +
geom_smooth(method = "lm") +
scale_fill_viridis_c() +
theme_bw()

- Now make a 3D scatterplot with axes corresponding to 'sales', 'TV' and 'radio'.
library(plotly)
Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
Attaching package: ‘plotly’
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
plot_ly(x=sales$Sales, y=sales$TV, z=sales$Radio, type="scatter3d", mode="markers")
`arrange_()` is deprecated as of dplyr 0.7.0.
Please use `arrange()` instead.
See vignette('programming') for more help
[90mThis warning is displayed once every 8 hours.[39m
[90mCall `lifecycle::last_warnings()` to see where this warning was generated.[39m
- The dataset has 200 rows. Divide it into a train set with 150 observations and a test set with 50 observations, i.e. use
sample(1:200, n = 150) to randomly choose row indices of the advertising dataset to include in the train set. The remaining indices should be used for the test set. Remember to choose and set the seed for randomization!
set.seed(123)
idx <- sample(1:200, size = 150)
train <- sales[idx, ]
test <- sales[-idx, ]
fit <- lm(Sales ~ TV, train)
pred_test <- predict(fit, test)
RMSE <- sqrt(mean((pred_test - test$Sales)^2))
cat("The RMSE for the simple regression model is:", RMSE, ".\n")
The RMSE for the simple regression model is: 3.43658 .
Fit a linear model to the training set, where the sales values are predicted by the amount of TV advertising. Print the summary of the fitted model. Then, predict the sales values for the test set and evaluate the test model accuracy in terms of root mean squared error (MSE), which measures the average level of error between the prediction and the true response. \[RMSE = \sqrt{\frac{1}{n} \sum\limits_{i = 1}^n(\hat y_i - y_i)^2}\]
Fit a multiple linerar regression model including all the variables 'TV', 'radio', 'newspaper' to model the 'sales' in the training set. Then, compute the predicted sales for the test set with the new model and evalued the RMSE.
Did the error decrease from the one correspodning to the previous model?
fit.mult <- lm(Sales ~ TV + Radio + Newspaper, train)
pred_test <- predict(fit.mult, test)
RMSE <- sqrt(mean((pred_test - test$Sales)^2))
cat("The RMSE for the multiple regression model is:", RMSE, ".\n")
The RMSE for the multiple regression model is: 1.909814 .
Yes, the RMSE decrease after including more predictors in the model.
- Look at the summary output for the multiple regression model and note which of the coefficient in the model is significant. Are all of them significant? If not refit the model including only the features found significant. Which of the models should you choose?
summary(fit.mult)
Call:
lm(formula = Sales ~ TV + Radio + Newspaper, data = train)
Residuals:
Min 1Q Median 3Q Max
-5.3581 -0.8916 0.2041 1.2089 2.6565
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 3.273848 0.339641 9.639 <2e-16 ***
TV 0.044485 0.001544 28.816 <2e-16 ***
Radio 0.190370 0.009643 19.741 <2e-16 ***
Newspaper -0.005491 0.006310 -0.870 0.386
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 1.612 on 146 degrees of freedom
Multiple R-squared: 0.9038, Adjusted R-squared: 0.9018
F-statistic: 457.1 on 3 and 146 DF, p-value: < 2.2e-16
It seems like the 'newspaper' predictor is not significant, so we will remove it.
fit.mult2 <- lm(Sales ~ TV + Radio, train)
pred_test <- predict(fit.mult2, test)
RMSE <- sqrt(mean((pred_test - test$Sales)^2))
cat("The RMSE for the new multiple regression model is:", RMSE, ".\n")
The RMSE for the new multiple regression model is: 1.890502 .
The second multiple regression model has a slightly better test error. We should not use more predictors than necessary, so 'newspaper' should be discarded from the model.
Doctor Visits
Data was collected on doctor visits from a sample of 5,190 people in the 1977/1978 Australian Health Survey. Cameron (1986) sought to explain the variation in doctor visits using one or more explanatory variables. The data can be found in an R data set from library(AER) accessible with the command data("DoctorVisits"). Variable descriptions can be found under help("DoctorVisits")
Explore the use of a zero-inflated model for this data. Begin with a histogram of the number of visits, some EDA, and fitting several models. Summarize your results. Compare your results with a standard Poisson.
library(AER)
data("DoctorVisits")
ggplot(DoctorVisits) +
geom_histogram(aes(x=visits))+
theme_bw()

ggplot(DoctorVisits) +
geom_histogram(aes(x=visits))+
facet_grid(gender~private)+
theme_bw()

df= DoctorVisits %>% group_by(visits) %>% summarise_if(is.numeric, mean)
Error in DoctorVisits %>% group_by(visits) %>% summarise_if(is.numeric, :
could not find function "%>%"
There are a lot of zeros --- hence the need for a zero-inflated model.
We begin by fitting a standard model and assessing goodness of fit.
summary(pois.m1 <- glm(formula = visits ~ ., family = poisson,
data = DoctorVisits))
Call:
glm(formula = visits ~ ., family = poisson, data = DoctorVisits)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.9502 -0.6858 -0.5747 -0.4852 5.7055
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.097821 0.101554 -20.657 < 2e-16 ***
genderfemale 0.156490 0.056139 2.788 0.00531 **
age 0.279123 0.165981 1.682 0.09264 .
income -0.187416 0.085478 -2.193 0.02834 *
illness 0.186156 0.018263 10.193 < 2e-16 ***
reduced 0.126690 0.005031 25.184 < 2e-16 ***
health 0.030683 0.010074 3.046 0.00232 **
privateyes 0.126498 0.071552 1.768 0.07707 .
freepooryes -0.438462 0.179799 -2.439 0.01474 *
freerepatyes 0.083640 0.092070 0.908 0.36365
nchronicyes 0.117300 0.066545 1.763 0.07795 .
lchronicyes 0.150717 0.082260 1.832 0.06692 .
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for poisson family taken to be 1)
Null deviance: 5634.8 on 5189 degrees of freedom
Residual deviance: 4380.1 on 5178 degrees of freedom
AIC: 6735.7
Number of Fisher Scoring iterations: 6
# Goodness-of-fit test
gof.ts = pois.m1$deviance
gof.pvalue = 1 - pchisq(gof.ts, pois.m1$df.residual)
gof.pvalue
[1] 1
Now we fit the zero-inflated model:
library(pscl)
summary(zi <- zeroinfl(formula = visits ~ health + age + gender | freepoor, data = DoctorVisits))
Call:
zeroinfl(formula = visits ~ health + age + gender | freepoor, data = DoctorVisits)
Pearson residuals:
Min 1Q Median 3Q Max
-0.7366 -0.4761 -0.4119 -0.3744 19.6376
Count model coefficients (poisson with log link):
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.224332 0.092322 -13.261 < 2e-16 ***
health 0.126664 0.009391 13.488 < 2e-16 ***
age 1.070226 0.143679 7.449 9.42e-14 ***
genderfemale 0.200220 0.060976 3.284 0.00102 **
Zero-inflation model coefficients (binomial with logit link):
Estimate Std. Error z value Pr(>|z|)
(Intercept) 0.14309 0.08059 1.775 0.0758 .
freepooryes 1.12007 0.27857 4.021 5.8e-05 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Number of iterations in BFGS optimization: 12
Log-likelihood: -3585 on 6 Df
vuong(pois.m1,zi)
Vuong Non-Nested Hypothesis Test-Statistic:
(test-statistic is asymptotically distributed N(0,1) under the
null that the models are indistinguishible)
-------------------------------------------------------------
Movies
Recall the movies data-frame we used ealier in the bootcamp. It contains information on movies from the last three decates, which was scrapped from the IMDB database.
library(dplyr)
url <- "https://raw.githubusercontent.com/Juanets/movie-stats/master/movies.csv"
movies <- tbl_df(read.csv(url))
- Generate a boxplot of runtimes for action movies and comedies with jittered points overlaid on top. You might consider setting collor, fill and alpha arguments to modify clarity and transparency of the plot.
library(tidyverse)
ggplot(movies %>% filter(genre %in% c("Action", "Comedy")),aes(x=genre, y = runtime)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(alpha=0.5, width = 0.1)
- Test a hypothesis that the action movies have higher mean runtime (length) than the comedies. Is the difference statistically greater than zero at significance level \(\alpha = 0.05\)?
comedies = movies %>% filter(genre== "Comedy")
actions = movies %>% filter(genre== "Action")
t.test(comedies$runtime, actions$runtime)
- Test the hypothesis that the scores are the same across movie types (keep the movie genre which have at least 20 movies). Plot the data before making a test of your choice. State all the assumptions that you are making when devising your test.
- The observations are obtained independently and randomly from the population defined by the factor levels
- The data of each factor level are normally distributed.
- These normal populations have a common variance. (Levene’s test can be used to check this.)
d = movies %>%
group_by(genre) %>%
summarise(
count = n(),
mean = mean(score, na.rm = TRUE),
sd = sd(score, na.rm = TRUE)
) %>% filter(count>=20)
genres_selected = d$genre
ggplot(movies %>% filter(genre %in% genres_selected), aes(x=genre, y=score, color=genre)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(alpha=0.1, width = 0.1) +
theme_bw()
res.aov <- aov(score ~ genre, data =movies %>% filter(genre %in% genres_selected))
# Summary of the analysis
summary(res.aov)
- Is there a reason to believe that the scores might differ according to genre? How would you test which one is different (do the test if you have reason to believe that this is the case).
TukeyHSD(res.aov)
Interpretation:
- diff: difference between means of the two groups
- lwr, upr: the lower and the upper end point of the confidence interval at 95% (default)
- p adj: p-value after adjustment for the multiple comparisons.
- Now, sppose your friend at Hollywood wants to know the recipe for making a movie with the best margin (gross-budget). Which genre and runtime should he aim for? Could you fit a model to try to help him out?
movies$margin = movies$gross - movies$budget
summary(mod <- lm(margin ~ genre+ runtime, data= movies))
LS0tCnRpdGxlOiAnTGFiIDIxOiBJbi1jbGFzczogR0xNcyBhbmQgVW5zdXBlcnZpc2VkIExlYXJuaW5nJwpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB5ZXMKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCmRhdGU6ICIwOC8xMy8yMDIwIgotLS0KCgojIEV4ZXJjaXNlIEk6IFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMKClJlY2FsbCB0aGUgYG10Y2Fyc2AgZGF0YXNldCB3ZSB3b3JrIHdpdGggYmVmb3JlLCB3aGljaCBjb21waXJzZXMgZnVlbCAKY29uc3VtcHRpb24gYW5kIG90aGVyIGFzcGVjdHMgb2YgZGVzaWduIGFuZCBwZXJmb3JtYW5jZSBmb3IgMzIgY2FycyBmcm9tIDE5NzQuClRoZSBkYXRhc2V0IGhhcyAxMSBkaW1lbnNpb25zLCB0aGF0IGlzIG1vcmUgdGhhbiBpdCBpcyBwb3NzaWJsZSB0byB2aXN1YWxpemUgYXQgCnRoZSBzYW1lLgoKYGBge3J9CmhlYWQobXRjYXJzKQpgYGAKCmEuIFVzZSBgcHJjb21wKClgIHRvIGNvbXB1dGUgYSBQQ0EgZm9yIGBtdGNhcnNgLiBSZW1lbWJlciB0byBzZXQgdGhlCnNjYWxlIHBhcmFtZXRlciwgYXMgdGhlIHZhcmlhYmxlcyBhcmUgaW4gZGlmZmVyZW50IHVuaXRzIGFuZCBoYXZlIGRpZmZlcmVudApyYW5nZXMKCmBgYHtyfQptdGNhcnMucGNhIDwtIHByY29tcChtdGNhcnMsIHNjYWxlPVRSVUUpCmBgYAoKYi4gR2VuZXJhdGUgYSBzY3JlZSBwbG90IGFuZCBub3RlIGhvdyBtYW55IGRpbWVuc2lvbnMgc2hvdWxkIHlvdSByZXRhaW4uCgpgYGB7cn0KbGlicmFyeShmYWN0b2V4dHJhKQoKIyBQZXJjZW50IG9mIHZhcmlhbmNlIGV4cGxhaW5lZDoKZnZpel9laWcobXRjYXJzLnBjYSkgCmBgYAoKYy4gQ29tcHV0ZSB0aGUgcGVyY2VudGFnZSBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgZWFjaCBvZiB0aGUgcHJpbmNpcGFsCmNvbXBvbmVudHMuCgpgYGB7cn0KZWlnIDwtIG10Y2Fycy5wY2Ekc2Rldl4yCih2YXIuZXhwIDwtIDEwMCplaWcvc3VtKGVpZykpCmBgYAoKZC4gR2VuZXJhdGUgYSBiaXBsb3QgZm9yIHRoZSBQQ0EgcHJvamVjdGlvbi4gVXNlIHRoZSBsb2FkaW5ncyBtYXRyaXggdG8gaW5zcGVjdAp3aGljaCB2YXJpYWJsZXMgY29udHJpYnV0ZXMgbW9zdCB0byBQQzEgYW5kIHdoaWNoIHRvIFBDMi4gV2hhdCBkbyB0aGUgUEMxIGFuZApQQzIgY29ycmVzcG9uZCB0bz8gSG93IGFyZSB0aGUgY2FycyBkaXN0cmlidXRlZCBvbiB0aGlzIHJlcHJlc2VudGF0aW9uPwpEb2VzIHRoZSAiY2FyIG1hcCIgbWFrZSBzZW5zZT8KCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQpmdml6X3BjYV9iaXBsb3QobXRjYXJzLnBjYSkgKyBjb29yZF9maXhlZCgpIApgYGAKCmBgYHtyfQptdGNhcnMucGNhJHJvdGF0aW9uCmBgYAoKYGBge3J9CmZ2aXpfY29udHJpYihtdGNhcnMucGNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDEpIApgYGAKCmBgYHtyfQpmdml6X2NvbnRyaWIobXRjYXJzLnBjYSwgY2hvaWNlID0gInZhciIsIGF4ZXMgPSAyKSAKYGBgCgoKCiMgRXhlcmNpc2UgMjogQ2x1c3RlciBBbmFseXNpcwoKIyMgUGFydCAxOiBrLW1lYW5zIGNsdXN0ZXJpbmcKCldlIHdpbGwgZ2VuZXJhdGUgc3ludGhldGljIGNsdXN0ZXJlZCBkYXRhIHRvIHVzZSBmb3Igay1tZWFucyBjbHVzdGVyaW5nLgpgYGB7cn0Kc2V0LnNlZWQoNDg5NTc2KQpOIDwtIDEwMDAKQzEgPC0gZGF0YS5mcmFtZShjbHVzdGVyID0gIkMxIiwgeCA9IHJub3JtKG4gPSBOLCBtZWFuID0gMSksIHkgPSBybm9ybShuID0gTiwgbWVhbiA9IDEpKQpDMiA8LSBkYXRhLmZyYW1lKGNsdXN0ZXIgPSAiQzIiLCB4ID0gcm5vcm0obiA9IE4sIG1lYW4gPSAtMiksIHkgPSBybm9ybShuID0gTiwgbWVhbiA9IC01KSkKQzMgPC0gZGF0YS5mcmFtZShjbHVzdGVyID0gIkMzIiwgeCA9IHJub3JtKG4gPSBOLCBtZWFuID0gNSksIHkgPSBybm9ybShuID0gTiwgbWVhbiA9IDEpKQpERiA8LSByYmluZChDMSwgQzIsIEMzKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoREYsIGFlcyh4LCB5LCBjb2xvciA9IGNsdXN0ZXIpKSArIAogIGdlb21fcG9pbnQoKQpgYGAKCmEuIEFwcGx5IGstbWVhbnMgd2l0aCBrID0gMyAoYXMgeW91IGtub3cgdGhlIHRydWUgbnVtYmVyIG9mIGNsdXN0ZXJzKS4KUHJpbmcgdGhlIGNsdXN0ZXIgY2VudGVycy4KCmBgYHtyfQprbWVhbnMucmVzIDwtIGttZWFucyh4ID0gREZbLCAtMV0sIGNlbnRlcnMgPSAzKQprbWVhbnMucmVzJGNlbnRlcnMKYGBgCgpiLiBQcmludCBhIGNvbmZ1c2lvbiBtYXAgdG8gY29tcGFyZSBrLW1lYW5zIGNsdXN0ZXIgYXNzaWdubWVudCB3aXRoCnRoZSB0cnVlIGNsdXN0ZXIgbGFiZWxzLgoKYGBge3J9CnRhYmxlKGttZWFucyA9IGttZWFucy5yZXMkY2x1c3RlciwgdHJ1ZSA9IERGJGNsdXN0ZXIpCmBgYAoKCmMuIEdlbmVyYXRlIGEgc2NhdHRlciBwbG90IG9mIHBvaW50cywgbm93IGNvbG9yZWQgYnkgdGhlIGNsdXN0ZXIgYXNzaWdubWVudC4KCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpERiRrbWVhbnMgPC0gZmFjdG9yKGttZWFucy5yZXMkY2x1c3RlcikKZ2dwbG90KERGLCBhZXMoeCwgeSkpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSwgYWVzKCBjb2xvciA9IGttZWFucykpICsgCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YS5mcmFtZSh4ID0ga21lYW5zLnJlcyRjZW50ZXJzWywgMV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGttZWFucy5yZXMkY2VudGVyc1ssIDJdKSwgc2l6ZSA9IDMsIGFlcyh4LCB5KSwgY29sb3IgPSAiQmxhY2siKQpgYGAKCmQuIE5vdyBwcmV0ZW5kIHRoYXQgeW91IGRvbid0IGtub3cgdGhlIHJlYWwgbnVtYmVyIG9mIGNsdXN0ZXJzLiBVc2UgayA9IDQKYW5kIHJlY29tcHV0ZSBrbWVhbnMuIFBsb3QgdGhlIHJlc3VsdHMgYW5kIHNlZSB3aGF0IGhhcHBlbmVkLgoKYGBge3J9CmttZWFucy5yZXMyIDwtIGttZWFucyh4ID0gREZbLCAtMV0sIGNlbnRlcnMgPSA0KQprbWVhbnMucmVzMiRjZW50ZXJzCmBgYAoKYGBge3J9CkRGJGttZWFuczIgPC0gZmFjdG9yKGttZWFucy5yZXMyJGNsdXN0ZXIpCmdncGxvdChERiwgYWVzKHgsIHksIGNvbG9yID0ga21lYW5zMikpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkKYGBgCgoKIyMgUGFydCAyOiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZwoKSW4gdGhpcyBleGVyY2lzZSB5b3Ugd2lsbCB5b3UgdXNlIGEgZGF0YXNldCBwdWJsaXNoZWQgaW4gYSBzdHVkeSBieQpbS2hhbiBldCBhbC4gMjAwMV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubTA2MDFfNjczKQp0byBwZXJmb3JtIGEgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2YgdGhlIHBhdGllbnRzIGluIHRoZSBzdHVkeSBiYXNlZApvbiB0aGVpciBvdmVyYWxsIGdlbmUgZXhwcmVzc2lvbiBkYXRhLgoKVGhpcyBkYXRhIHNldCBjb25zaXN0cyBvZiBleHByZXNzaW9uIGxldmVscyBmb3IgMiwzMDggZ2VuZXMuClRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXRzIGNvbnNpc3Qgb2YgNjMgYW5kIDIwIG9ic2VydmF0aW9ucyAodGlzc3VlIHNhbXBsZXMpIApyZXNwZWN0aXZlbHkuCgpIZXJlLCB3ZSB3aWxsIHVzZSB0aGUgdHJhaW4gc2V0LCBhcyB3ZSBub3cgYXJlIG9ubHkgaW50ZXJlc3RlZCBpbgpsZWFybmluZyBob3cgYGhjbHVzdCgpYCB3b3Jrcy4gRmlyc3QsIGxvYWQgdGhlIGBJU0xSYCB3aGVyZSB0aGUKZGF0YSBpcyBhdmFpbGFibGUuIFRoZSBnZW5lIGV4cHJlc3Npb24gZGF0YSBpcyBhdmFpbGFibGUgaW4gYW4gb2JqZWN0CmBLaGFuJHh0cmFpbmA7IHlvdSBjYW4gbGVhcm4gbW9yZSBhYm91dCB0aGUgZGF0YSBzZXQgYnkgdHlwaW5nIGluIGA/S2hhbmAKYWZ0ZXIgbG9hZGluZyBgSVNMUmAgcGFja2FnZS4KCmBgYHtyfQpsaWJyYXJ5KElTTFIpCmdlbmUuZXhwcmVzc2lvbiA8LSBLaGFuJHh0cmFpbgpkaW0oZ2VuZS5leHByZXNzaW9uKQpgYGAKCmEuIENvbXB1dGUgYSAoRXVjbGlkZWFuKSBkaXN0YW5jZSBtYXRyaXggYmV0d2VlbiBlYWNoIHBhaXIgb2Ygc2FtcGxlcy4KCmBgYHtyfQpEIDwtIGRpc3QoZ2VuZS5leHByZXNzaW9uKQpgYGAKCmIuIFBlcmZvcm0gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdXNpbmcgYXZlcmFnZSBsaW5rYWdlLgoKYGBge3J9CmtoYW4uaGNsdXN0IDwtIGhjbHVzdChELCBtZXRob2QgPSAiYXZlcmFnZSIpCmBgYAoKYy4gUGxvdCBhIGRlbmRyb2dyYW0gYXNzb2NpYXRlZCB3aXRoIHRoZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyB5b3UganVzdApjb21wdXRlZC4gSW4gdGhpcyBleGFtcGxlLCB5b3UgYWN0dWFsbHkgaGF2ZSB0aGUgbGFibGVzIG9mIHRoZSB0aXNzdWUgc2FtcGxlcywKaG93ZXZlciwgdGhlIGFsZ29yaXRobXMgd2FzIGJsaW5kZWQgdG8gdGhlbS4gQnkgYWRkaW5nIGxhYmVscyB0byB0aGUgZGVuZHJvZ3JhbQpjb3JyZXNwb25kaW5nIHRvIGBLaGFuJHl0cmFpbmAsIGNoZWNrIGlmIHRoZSBjbHVzdGVyaW5nIHBlcmZvcm1lZCBncm91cHMgdGhlIApvYnNlcnZhdGlvbnMgZnJvbSBzYW1lIHR1bW9yIGNsYXNzIG5lYXJieS4gCgpgYGB7cn0KcGxvdChraGFuLmhjbHVzdCwgbGFiZWxzID0gS2hhbiR5dHJhaW4pCmBgYAoKCiMjIEV4ZXJjaXNlIEV4dHJhOiAyRCB2aXN1YWxpemF0aW9uIG9mIE1OSVNUIGRhdGEKCiogRG93bmxvYWQgTU5JU1QgZGF0YSBvZiB0aGUgZGlnaXRzIGltYWdlcyBmcm9tIApbS2FnZ2xlIGNvbXBldGl0aW9uXShodHRwczovL3d3dy5rYWdnbGUuY29tL2MvZGlnaXQtcmVjb2duaXplcikuCiogVGhlIGNvZGUgaXMgYWRhcHRlZCBmcm9tIHRoZSBvbmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZ29zcHVyc2dvL2RpZ2l0LXJlY29nbml6ZXIvY2x1c3RlcnMtaW4tMmQtd2l0aC10c25lLXZzLXBjYS9jb2RlKS4gCgpUaGUgZmlsZXMgYXJlIGRhdGEgb24gdGhlIDI4eDI4IHBpeGVsCmltYWdlcyBvZiBkaWdpdHMgKDAtOSkuIFRoZSBkYXRhIGlzIGNvbXBvc2VkIG9mOgoKKiBgbGFiZWxgIGNvbHVtbiBkZW5vdGluZyB0aGUgZGlnaXQgb24gdGhlIGltYWdlCiogYHBpeGVsMGAgdGhyb3VnaCBgcGl4ZWw3ODNgIGNvbnRhaW4gaW5mb3JtYXRpb24gb24gdGhlIHBpeGVsIGludGVuc2l0eQoob24gdGhlIHNjYWxlIG9mIDAtMjU1KSwgYW5kIHRvZ2V0aGVyIGZvcm0gdGhlIHZlY3Rvcml6ZWQgdmVyc2lvbiBvZiAKdGhlIDI4eDI4IHBpeGVsIGRpZ2l0IGltYWdlCgohW10obW5pc3RFeGFtcGxlcy5wbmcpCgpEb3dubG9hZCB0aGUgZGF0YSBmcm9tIHRoZSBjb3Vyc2UgcmVwb3NpdG9yeToKCmBgYHtyfQojIGxvYWQgdGhlIGFscmVhZHkgc3Vic2V0dGVkIE1OSVNUIGRhdGEuCm1uaXN0LnVybCA8LSAiaHR0cHM6Ly9naXRodWIuY29tL2NtZTE5NS9jbWUxOTUuZ2l0aHViLmlvL3Jhdy9tYXN0ZXIvYXNzZXRzL2RhdGEvbW5pc3Rfc21hbGwuY3N2Igp0cmFpbiA8LSByZWFkLmNzdihtbmlzdC51cmwsIHJvdy5uYW1lcyA9IDEpCmRpbSh0cmFpbikKdHJhaW5bMToxMCwgMToxMF0KYGBgCgphLiBDb21wdXRlIGFuZCB0aGUgUENBIGZvciB0aGUgZGF0YS4gVGhlbiwgZXh0cmFjdCB0aGUgZmlyc3QgdHdvIHByaW5jaXBhbApjb21wb25lbnQgc2NvcmVzIGZvciB0aGUgZGF0YS4KCmBgYHtyfQojIGNvbXBhcmUgd2l0aCBwY2EKcGNhIDwtIHByY29tcCh0cmFpblssLTFdKQpjb29yZC5wY2EgPC0gZGF0YS5mcmFtZShwY2EkeFssIDE6Ml0pCmNvb3JkLnBjYSRsYWJlbCA8LSBmYWN0b3IodHJhaW4kbGFiZWwpCmBgYAoKYi4gUGxvdCB0aGUgMkQgcHJpbmNpcGFsIGNvbXBvbmVudCBzY29yZXMgbWF0cml4LgoKYGBge3J9CmdncGxvdChjb29yZC5wY2EsIGFlcyh4PSBQQzEsIHkgPSBQQzIpKSArIGdndGl0bGUoIlBDQSIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbGFiZWwsIGNvbG9yID0gbGFiZWwpLCBhbHBoYSA9IDAuOCkKYGBgCgpjLiBDb21wdXRlIGEgdFNORSBlbWJlZGRpbmcuCmBgYHtyfQojIFVzZSB0c25lCmxpYnJhcnkoUnRzbmUpCnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnRzbmUgPC0gUnRzbmUodHJhaW5bLC0xXSwgZGltcyA9IDIsIHBlcnBsZXhpdHk9MzAsIAogICAgICAgICAgICAgIHZlcmJvc2U9RkFMU0UsIG1heF9pdGVyID0gNTAwKQpjb29yZC50c25lIDwtIGRhdGEuZnJhbWUodHNuZSRZKQpjb29yZC50c25lJGxhYmVsIDwtIGZhY3Rvcih0cmFpbiRsYWJlbCkKYGBgCgpkLiBWaXN1YWxpemUgdGhlIHRTTkUgMkQgcHJvamVjdGlvbi4KCmBgYHtyfQpnZ3Bsb3QoY29vcmQudHNuZSwgYWVzKHg9IFgxLCB5ID0gWDIpKSArIGdndGl0bGUoInRTTkUiKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGxhYmVsLCBjb2xvciA9IGxhYmVsKSwgYWxwaGEgPSAwLjgpCmBgYAoKZS4gV2hhdCBkbyB5b3Ugb2JzZXJ2ZT8gSG93IGRvZXMgdFNORSBjb21wYXJlIHdpdGggUENBIGluIHRoaXMgY2FzZT8KCnRTTkUgc2VlbXMgdG8gYmUgbXVjaCBiZXR0ZXIgYXQgc2VwYXJhdGluZyBkaWdpdHMgZnJvbSBlYWNoIG90aGVyCgoKCgoKIyBFeGVyY2lzZSA0OiAgQWRzCgphLiBSZWFkIHRoZSBkYXRhIGZyb20gImh0dHBzOi8vYmlveC1yYm9vdGNhbXAuZ2l0aHViLmlvL2Jpb3gtcmJvb3RjYW1wLmdpdGh1Yi5pby9hc3NldHMvbGVjdHVyZXMvdW5zdXBlcnZpc2VkX2xlYXJuaW5nL0FkdmVydGlzaW5nLmNzdiIKY29udGFpbmluZyBpbmZvcm1hdGlvbiBvbiBzYWxlcyBvZiBhIHByb2R1Y3QgYW5kIHRoZSBhbW91bnQgc3BlbnQgb24gYWR2ZXJ0aXNpbmcKdXNpbmcgZGlmZmVyZW50IG1lZGlhIGNoYW5uZWxzLgoKYGBge3J9CnVybCA8LSAiaHR0cHM6Ly9iaW94LXJib290Y2FtcC5naXRodWIuaW8vYmlveC1yYm9vdGNhbXAuZ2l0aHViLmlvL2Fzc2V0cy9sZWN0dXJlcy91bnN1cGVydmlzZWRfbGVhcm5pbmcvQWR2ZXJ0aXNpbmcuY3N2IgpzYWxlcyA8LSByZWFkLmNzdih1cmwpCnNhbGVzCmBgYAoKCgpiLiBHZW5lcmF0ZSBhIHNjYXR0ZXJwbG90IG9mIHNhbGVzIGFnYWluc3QgdGhlIGFtb3VudCBvZiBUViBhZHZlcnRpc2luZyBhbmQgCmFkZCBhIGxpbmVhciBmaXQgbGluZS4KCmBgYHtyfQpnZ3Bsb3Qoc2FsZXMsIGFlcyh4ID0gVFYsIHkgPSBTYWxlcykpICsKICAgIGdlb21fcG9pbnQoYWVzKGZpbGwgPSBSYWRpbyksIGNvbG9yID0gImdyZXkiLCBwY2ggPSAyMSwgc2l6ZSA9IDMpICsKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogICAgdGhlbWVfYncoKQpgYGAKCgoKYy4gTm93IG1ha2UgYSAzRCBzY2F0dGVycGxvdCB3aXRoIGF4ZXMgY29ycmVzcG9uZGluZyB0byAnc2FsZXMnLCAnVFYnCmFuZCAncmFkaW8nLgoKYGBge3J9CmxpYnJhcnkocGxvdGx5KQpwbG90X2x5KHg9c2FsZXMkU2FsZXMsIHk9c2FsZXMkVFYsIHo9c2FsZXMkUmFkaW8sIHR5cGU9InNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiKQpgYGAKCgpkLiBUaGUgZGF0YXNldCBoYXMgMjAwIHJvd3MuIERpdmlkZSBpdCBpbnRvIGEgdHJhaW4gc2V0IHdpdGggMTUwIG9ic2VydmF0aW9ucwphbmQgYSB0ZXN0IHNldCB3aXRoIDUwIG9ic2VydmF0aW9ucywgaS5lLiB1c2UgYHNhbXBsZSgxOjIwMCwgbiA9IDE1MClgIHRvCnJhbmRvbWx5IGNob29zZSByb3cgaW5kaWNlcyBvZiB0aGUgYWR2ZXJ0aXNpbmcgZGF0YXNldCB0byBpbmNsdWRlIGluIHRoZSAKdHJhaW4gc2V0LiBUaGUgcmVtYWluaW5nIGluZGljZXMgc2hvdWxkIGJlIHVzZWQgZm9yIHRoZSB0ZXN0IHNldC4gUmVtZW1iZXIKdG8gY2hvb3NlIGFuZCBzZXQgdGhlIHNlZWQgZm9yIHJhbmRvbWl6YXRpb24hCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQppZHggPC0gc2FtcGxlKDE6MjAwLCBzaXplID0gMTUwKQp0cmFpbiA8LSBzYWxlc1tpZHgsIF0KdGVzdCA8LSBzYWxlc1staWR4LCBdCgpmaXQgPC0gbG0oU2FsZXMgfiBUViwgdHJhaW4pCnByZWRfdGVzdCA8LSBwcmVkaWN0KGZpdCwgdGVzdCkKClJNU0UgPC0gc3FydChtZWFuKChwcmVkX3Rlc3QgLSB0ZXN0JFNhbGVzKV4yKSkKCmNhdCgiVGhlIFJNU0UgZm9yIHRoZSBzaW1wbGUgcmVncmVzc2lvbiBtb2RlbCBpczoiLCBSTVNFLCAiLlxuIikKYGBgCgoKZS4gRml0IGEgbGluZWFyIG1vZGVsIHRvIHRoZSB0cmFpbmluZyBzZXQsIHdoZXJlIHRoZSBzYWxlcyB2YWx1ZXMgYXJlCnByZWRpY3RlZCBieSB0aGUgYW1vdW50IG9mIFRWIGFkdmVydGlzaW5nLiBQcmludCB0aGUgc3VtbWFyeSBvZiB0aGUgZml0dGVkIG1vZGVsLgpUaGVuLCBwcmVkaWN0IHRoZSBzYWxlcyB2YWx1ZXMgZm9yIHRoZSB0ZXN0IHNldCBhbmQgZXZhbHVhdGUgdGhlIHRlc3QgbW9kZWwgCmFjY3VyYWN5IGluIHRlcm1zIG9mIHJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIChNU0UpLCB3aGljaCBtZWFzdXJlcyAKdGhlIGF2ZXJhZ2UgbGV2ZWwgb2YgZXJyb3IgYmV0d2VlbiB0aGUgcHJlZGljdGlvbiBhbmQgdGhlIHRydWUgcmVzcG9uc2UuCiQkUk1TRSA9IFxzcXJ0e1xmcmFjezF9e259IFxzdW1cbGltaXRzX3tpID0gMX1ebihcaGF0IHlfaSAtIHlfaSleMn0kJAoKZi4gRml0IGEgbXVsdGlwbGUgbGluZXJhciByZWdyZXNzaW9uIG1vZGVsIGluY2x1ZGluZyBhbGwgdGhlIHZhcmlhYmxlcyAnVFYnLAoncmFkaW8nLCAnbmV3c3BhcGVyJyB0byBtb2RlbCB0aGUgJ3NhbGVzJyBpbiB0aGUgdHJhaW5pbmcgc2V0LiBUaGVuLCBjb21wdXRlIAp0aGUgcHJlZGljdGVkIHNhbGVzIGZvciB0aGUgdGVzdCBzZXQgd2l0aCB0aGUgbmV3IG1vZGVsIGFuZCBldmFsdWVkIHRoZSBSTVNFLiAgCkRpZCB0aGUgZXJyb3IgZGVjcmVhc2UgZnJvbSB0aGUgb25lIGNvcnJlc3BvZG5pbmcgdG8gdGhlIHByZXZpb3VzIG1vZGVsPwoKCmBgYHtyfQpmaXQubXVsdCA8LSBsbShTYWxlcyB+IFRWICsgUmFkaW8gKyBOZXdzcGFwZXIsIHRyYWluKQpwcmVkX3Rlc3QgPC0gcHJlZGljdChmaXQubXVsdCwgdGVzdCkKClJNU0UgPC0gc3FydChtZWFuKChwcmVkX3Rlc3QgLSB0ZXN0JFNhbGVzKV4yKSkKY2F0KCJUaGUgUk1TRSBmb3IgdGhlIG11bHRpcGxlIHJlZ3Jlc3Npb24gbW9kZWwgaXM6IiwgUk1TRSwgIi5cbiIpCmBgYAoKWWVzLCB0aGUgUk1TRSBkZWNyZWFzZSBhZnRlciBpbmNsdWRpbmcgbW9yZSBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbC4KCmcuIExvb2sgYXQgdGhlIHN1bW1hcnkgb3V0cHV0IGZvciB0aGUgbXVsdGlwbGUgcmVncmVzc2lvbiBtb2RlbCBhbmQgbm90ZSB3aGljaCAKb2YgdGhlIGNvZWZmaWNpZW50IGluIHRoZSBtb2RlbCBpcyBzaWduaWZpY2FudC4gQXJlIGFsbCBvZiB0aGVtIHNpZ25pZmljYW50PwpJZiBub3QgcmVmaXQgdGhlIG1vZGVsIGluY2x1ZGluZyBvbmx5IHRoZSBmZWF0dXJlcyBmb3VuZCBzaWduaWZpY2FudC4KV2hpY2ggb2YgdGhlIG1vZGVscyBzaG91bGQgeW91IGNob29zZT8gCgoKYGBge3J9CnN1bW1hcnkoZml0Lm11bHQpCmBgYAoKSXQgc2VlbXMgbGlrZSB0aGUgJ25ld3NwYXBlcicgcHJlZGljdG9yIGlzIG5vdCBzaWduaWZpY2FudCwgc28Kd2Ugd2lsbCByZW1vdmUgaXQuCgpgYGB7cn0KZml0Lm11bHQyIDwtIGxtKFNhbGVzIH4gVFYgKyBSYWRpbywgdHJhaW4pCnByZWRfdGVzdCA8LSBwcmVkaWN0KGZpdC5tdWx0MiwgdGVzdCkKClJNU0UgPC0gc3FydChtZWFuKChwcmVkX3Rlc3QgLSB0ZXN0JFNhbGVzKV4yKSkKY2F0KCJUaGUgUk1TRSBmb3IgdGhlIG5ldyBtdWx0aXBsZSByZWdyZXNzaW9uIG1vZGVsIGlzOiIsIFJNU0UsICIuXG4iKQpgYGAKClRoZSBzZWNvbmQgbXVsdGlwbGUgcmVncmVzc2lvbiBtb2RlbCBoYXMgYSBzbGlnaHRseSBiZXR0ZXIgdGVzdCBlcnJvci4KV2Ugc2hvdWxkIG5vdCB1c2UgbW9yZSBwcmVkaWN0b3JzIHRoYW4gbmVjZXNzYXJ5LCBzbyAnbmV3c3BhcGVyJwpzaG91bGQgYmUgZGlzY2FyZGVkIGZyb20gdGhlIG1vZGVsLgoKCiMgRG9jdG9yIFZpc2l0cwoKRGF0YSB3YXMgY29sbGVjdGVkIG9uIGRvY3RvciB2aXNpdHMgZnJvbSBhIHNhbXBsZSBvZiA1LDE5MCBwZW9wbGUgaW4gdGhlIDE5NzcvMTk3OCBBdXN0cmFsaWFuIEhlYWx0aCBTdXJ2ZXkuIENhbWVyb24gKDE5ODYpIHNvdWdodCB0byBleHBsYWluIHRoZSB2YXJpYXRpb24gaW4gZG9jdG9yIHZpc2l0cyB1c2luZyBvbmUgb3IgbW9yZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMuIFRoZSBkYXRhIGNhbiBiZSBmb3VuZCBpbiBhbiBSIGRhdGEgc2V0IGZyb20gYGxpYnJhcnkoQUVSKWAgYWNjZXNzaWJsZSB3aXRoIHRoZSBjb21tYW5kIGBkYXRhKCJEb2N0b3JWaXNpdHMiKWAuIFZhcmlhYmxlIGRlc2NyaXB0aW9ucyBjYW4gYmUgZm91bmQgdW5kZXIgYGhlbHAoIkRvY3RvclZpc2l0cyIpYAoKRXhwbG9yZSB0aGUgdXNlIG9mIGEgemVyby1pbmZsYXRlZCBtb2RlbCBmb3IgdGhpcyBkYXRhLiBCZWdpbiB3aXRoIGEgaGlzdG9ncmFtIG9mIHRoZSBudW1iZXIgb2YgdmlzaXRzLCBzb21lIEVEQSwgYW5kIGZpdHRpbmcgc2V2ZXJhbCBtb2RlbHMuIFN1bW1hcml6ZSB5b3VyIHJlc3VsdHMuIENvbXBhcmUgeW91ciByZXN1bHRzIHdpdGggYSBzdGFuZGFyZCBQb2lzc29uLgoKYGBge3J9CmxpYnJhcnkoQUVSKQpkYXRhKCJEb2N0b3JWaXNpdHMiKQoKZ2dwbG90KERvY3RvclZpc2l0cykgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4PXZpc2l0cykpKwogIHRoZW1lX2J3KCkKCmdncGxvdChEb2N0b3JWaXNpdHMpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeD12aXNpdHMpKSsKICBmYWNldF9ncmlkKGdlbmRlcn5wcml2YXRlKSsKICB0aGVtZV9idygpCgpkZj0gRG9jdG9yVmlzaXRzICU+JSBncm91cF9ieSh2aXNpdHMpICU+JSBzdW1tYXJpc2VfaWYoaXMubnVtZXJpYywgbWVhbikKZ2dwbG90KGRmKSArCiAgZ2VvbV9wb2ludChhZXMoeT1sb2codmlzaXRzKzEpLCB4PWFnZSkpKwogIHRoZW1lX2J3KCkKCmdncGxvdChkZikgKwogIGdlb21fcG9pbnQoYWVzKHk9bG9nKHZpc2l0cysxKSwgeD1pbmNvbWUpKSsKICB0aGVtZV9idygpCgpgYGAKVGhlcmUgYXJlIGEgbG90IG9mIHplcm9zIC0tLSBoZW5jZSB0aGUgbmVlZCBmb3IgYSB6ZXJvLWluZmxhdGVkIG1vZGVsLgoKV2UgYmVnaW4gYnkgZml0dGluZyBhIHN0YW5kYXJkIG1vZGVsIGFuZCBhc3Nlc3NpbmcgZ29vZG5lc3Mgb2YgZml0LgoKYGBge3J9CgpzdW1tYXJ5KHBvaXMubTEgPC0gZ2xtKGZvcm11bGEgPSB2aXNpdHMgfiAuLCBmYW1pbHkgPSBwb2lzc29uLCAKICAgIGRhdGEgPSBEb2N0b3JWaXNpdHMpKQoKIyBHb29kbmVzcy1vZi1maXQgdGVzdApnb2YudHMgPSBwb2lzLm0xJGRldmlhbmNlCmdvZi5wdmFsdWUgPSAxIC0gcGNoaXNxKGdvZi50cywgcG9pcy5tMSRkZi5yZXNpZHVhbCkKZ29mLnB2YWx1ZQpgYGAKTm93IHdlIGZpdCB0aGUgemVyby1pbmZsYXRlZCBtb2RlbDoKCgpgYGB7cn0KbGlicmFyeShwc2NsKQpzdW1tYXJ5KHppIDwtIHplcm9pbmZsKGZvcm11bGEgPSB2aXNpdHMgfiBoZWFsdGggKyBhZ2UgKyBnZW5kZXIgfCBmcmVlcG9vciwgZGF0YSA9IERvY3RvclZpc2l0cykpCmBgYAoKYGBge3J9CnZ1b25nKHBvaXMubTEsemkpCgpgYGAKIyBNb3ZpZXMKClJlY2FsbCB0aGUgbW92aWVzIGRhdGEtZnJhbWUgd2UgdXNlZCBlYWxpZXIgaW4gdGhlIGJvb3RjYW1wLiBJdCBjb250YWlucwppbmZvcm1hdGlvbiBvbiBtb3ZpZXMgZnJvbSB0aGUgbGFzdCB0aHJlZSBkZWNhdGVzLCB3aGljaCB3YXMgc2NyYXBwZWQgZnJvbQp0aGUgSU1EQiBkYXRhYmFzZS4KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQp1cmwgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9KdWFuZXRzL21vdmllLXN0YXRzL21hc3Rlci9tb3ZpZXMuY3N2Igptb3ZpZXMgPC0gdGJsX2RmKHJlYWQuY3N2KHVybCkpCmBgYAoKYS4gR2VuZXJhdGUgYSBib3hwbG90IG9mIHJ1bnRpbWVzIGZvciBhY3Rpb24gbW92aWVzIGFuZCBjb21lZGllcwp3aXRoIGppdHRlcmVkIHBvaW50cyBvdmVybGFpZCBvbiB0b3AuIFlvdSBtaWdodCBjb25zaWRlciBzZXR0aW5nIGNvbGxvciwgCmZpbGwgYW5kIGFscGhhIGFyZ3VtZW50cyB0byBtb2RpZnkgY2xhcml0eSBhbmQgdHJhbnNwYXJlbmN5IG9mIHRoZSBwbG90LgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpnZ3Bsb3QobW92aWVzICU+JSBmaWx0ZXIoZ2VucmUgJWluJSBjKCJBY3Rpb24iLCAiQ29tZWR5IikpLGFlcyh4PWdlbnJlLCB5ID0gcnVudGltZSkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGE9MC41LCB3aWR0aCA9IDAuMSkKYGBgCgpiLiBUZXN0IGEgaHlwb3RoZXNpcyB0aGF0IHRoZSBhY3Rpb24gbW92aWVzIGhhdmUgaGlnaGVyIG1lYW4gcnVudGltZSAobGVuZ3RoKQp0aGFuIHRoZSBjb21lZGllcy4gSXMgdGhlIGRpZmZlcmVuY2Ugc3RhdGlzdGljYWxseSBncmVhdGVyIHRoYW4gemVybwphdCBzaWduaWZpY2FuY2UgbGV2ZWwgJFxhbHBoYSA9IDAuMDUkPwoKCmBgYHtyfQpjb21lZGllcyA9IG1vdmllcyAlPiUgZmlsdGVyKGdlbnJlPT0gIkNvbWVkeSIpCmFjdGlvbnMgPSBtb3ZpZXMgJT4lIGZpbHRlcihnZW5yZT09ICJBY3Rpb24iKQp0LnRlc3QoY29tZWRpZXMkcnVudGltZSwgYWN0aW9ucyRydW50aW1lKQpgYGAKCmMuIFRlc3QgdGhlIGh5cG90aGVzaXMgdGhhdCB0aGUgc2NvcmVzIGFyZSB0aGUgc2FtZSBhY3Jvc3MgbW92aWUgdHlwZXMgKGtlZXAgdGhlIG1vdmllIGdlbnJlIHdoaWNoIGhhdmUgYXQgbGVhc3QgMjAgbW92aWVzKS4gUGxvdCB0aGUgZGF0YSBiZWZvcmUgbWFraW5nIGEgdGVzdCBvZiB5b3VyIGNob2ljZS4gU3RhdGUgYWxsIHRoZSBhc3N1bXB0aW9ucyB0aGF0IHlvdSBhcmUgbWFraW5nIHdoZW4gZGV2aXNpbmcgeW91ciB0ZXN0LgoKKyBUaGUgb2JzZXJ2YXRpb25zIGFyZSBvYnRhaW5lZCBpbmRlcGVuZGVudGx5IGFuZCByYW5kb21seSBmcm9tIHRoZSBwb3B1bGF0aW9uIGRlZmluZWQgYnkgdGhlIGZhY3RvciBsZXZlbHMKKyBUaGUgZGF0YSBvZiBlYWNoIGZhY3RvciBsZXZlbCBhcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuCisgVGhlc2Ugbm9ybWFsIHBvcHVsYXRpb25zIGhhdmUgYSBjb21tb24gdmFyaWFuY2UuIChMZXZlbmXigJlzIHRlc3QgY2FuIGJlIHVzZWQgdG8gY2hlY2sgdGhpcy4pCgpgYGB7cn0KCmQgPSBtb3ZpZXMgJT4lCiAgZ3JvdXBfYnkoZ2VucmUpICU+JQogIHN1bW1hcmlzZSgKICAgIGNvdW50ID0gbigpLAogICAgbWVhbiA9IG1lYW4oc2NvcmUsIG5hLnJtID0gVFJVRSksCiAgICBzZCA9IHNkKHNjb3JlLCBuYS5ybSA9IFRSVUUpCiAgKSAlPiUgZmlsdGVyKGNvdW50Pj0yMCkKZ2VucmVzX3NlbGVjdGVkID0gZCRnZW5yZQpnZ3Bsb3QobW92aWVzICU+JSBmaWx0ZXIoZ2VucmUgJWluJSBnZW5yZXNfc2VsZWN0ZWQpLCBhZXMoeD1nZW5yZSwgeT1zY29yZSwgY29sb3I9Z2VucmUpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKwogIGdlb21faml0dGVyKGFscGhhPTAuMSwgd2lkdGggPSAwLjEpICsKICB0aGVtZV9idygpCgoKcmVzLmFvdiA8LSBhb3Yoc2NvcmUgfiBnZW5yZSwgZGF0YSA9bW92aWVzICU+JSBmaWx0ZXIoZ2VucmUgJWluJSBnZW5yZXNfc2VsZWN0ZWQpKQojIFN1bW1hcnkgb2YgdGhlIGFuYWx5c2lzCnN1bW1hcnkocmVzLmFvdikKCmBgYAoKCmQuIElzIHRoZXJlIGEgcmVhc29uIHRvIGJlbGlldmUgdGhhdCB0aGUgc2NvcmVzIG1pZ2h0IGRpZmZlciBhY2NvcmRpbmcgdG8gZ2VucmU/IEhvdyB3b3VsZCB5b3UgdGVzdCB3aGljaCBvbmUgaXMgZGlmZmVyZW50IChkbyB0aGUgdGVzdCBpZiB5b3UgaGF2ZSByZWFzb24gdG8gYmVsaWV2ZSB0aGF0IHRoaXMgaXMgdGhlIGNhc2UpLgoKYGBge3J9ClR1a2V5SFNEKHJlcy5hb3YpCmBgYAoKSW50ZXJwcmV0YXRpb246CgorIGRpZmY6IGRpZmZlcmVuY2UgYmV0d2VlbiBtZWFucyBvZiB0aGUgdHdvIGdyb3VwcworIGx3ciwgdXByOiB0aGUgbG93ZXIgYW5kIHRoZSB1cHBlciBlbmQgcG9pbnQgb2YgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgYXQgOTUlIChkZWZhdWx0KQorIHAgYWRqOiBwLXZhbHVlIGFmdGVyIGFkanVzdG1lbnQgZm9yIHRoZSBtdWx0aXBsZSBjb21wYXJpc29ucy4KCgplLiBOb3csIHNwcG9zZSB5b3VyIGZyaWVuZCBhdCBIb2xseXdvb2Qgd2FudHMgdG8ga25vdyB0aGUgcmVjaXBlIGZvciBtYWtpbmcgYSBtb3ZpZSB3aXRoIHRoZSBiZXN0IG1hcmdpbiAoZ3Jvc3MtYnVkZ2V0KS4gV2hpY2ggZ2VucmUgYW5kIHJ1bnRpbWUgc2hvdWxkIGhlIGFpbSBmb3I/IENvdWxkIHlvdSBmaXQgYSBtb2RlbCB0byB0cnkgdG8gaGVscCBoaW0gb3V0PwoKCmBgYHtyfQptb3ZpZXMkbWFyZ2luID0gbW92aWVzJGdyb3NzIC0gbW92aWVzJGJ1ZGdldApzdW1tYXJ5KG1vZCA8LSBsbShtYXJnaW4gfiBnZW5yZSsgcnVudGltZSwgZGF0YT0gbW92aWVzKSkKCmBgYAo=